/*
 * Copyright (c) 2017, MegaEase
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package canary

import (
	"github.com/megaease/easegress/pkg/context"

	yaml "gopkg.in/yaml.v2"
)

// Rule is the A/B testing rule specified by user.
type Rule struct {
	// TagKey is the HTTP header will be added if rule matched.
	TagKey string `yaml:"tagKey"`
	// TagValue is the TagKey header's value.
	TagValue string `yaml:"tagValue"`
	// Description describes the rule.
	Description string `yaml:"description"`
	// Conditions is the rule's target, it's a conditional expressions beginning with 'if'.
	//
	// Supports conditional operator:
	// ==, !=, >=, <=, >, <, in, mod
	// mod is a special keyword which means randomly pick up request according
	// to the value in a specified rate. The value after mod op will be set to 0
	// if it is < 0. And it will be mod 100 before using it.
	// e.g., RealIP mod '10'
	//
	// Supports logic operator:
	// ||, &&, and ()
	//
	// Supports four types of sources:
	// Header, Cookie, Jwt, RealIP
	//
	// We get Jwt value from HTTP header Authorization,
	// by default, Jwt token will be put into this header which has this form:
	// Authorization: Bearer <token>.
	//
	// TODO may support multiple values for on header in future.
	//
	// e.g.,
	// Header.City in 'a, b, c' && (Header.Gender == 'male' || Cookie.UserType == 'VIP')
	// || Jwt.key != 'val' || RealIP mod '10'
	//
	// In practice, there only one legal 'mod', RealIP (TODO supports more approaches).
	// And it's better to put RealIP mod <x> in the end, because mod operation need
	// to calculate hash, it's much expensive than other condition. If there is
	// short circuit, we won't need to do such calculation.
	Conditions string `yaml:"conditions"`
	// isMatch is generated by Conditions, it's used for deciding to add Tag or not.
	// If true, adding the tag.
	isMatch matcher
}

// parse parses json format Rule and generating the matchFunc.
func (r *Rule) parse(p []byte) (err error) {
	err = yaml.Unmarshal(p, r)
	if err != nil {
		return
	}

	r.isMatch, err = makeMatcher(r.Conditions)
	if err != nil {
		return err
	}

	return
}

// doMatch tries to match conditions,
// if true, add tag.
// if false, do nothing.
func (r *Rule) doMatch(ctx context.HTTPContext) {
	if r.isMatch(&sourceData{
		req:      ctx.Request().Std(),
		clientIP: ctx.Request().RealIP(),
	}) {
		ctx.Request().Header().Set(r.TagKey, r.TagValue)
	}
}
